{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Design - Classical Variables and Operations"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Quantum variables and operations lie at the heart of the Qmod language but they are not enough for designing any quantum algorithm one has in mind. To complete the picture we need also the ability to perform classical control flow with classical operations over classical variables. These are the operations that a typical Python user would expect to have like `for` loops and `if-else` statements over `int` or `float` variables.\n",
    "\n",
    "In Qmod, there are equivalent types for the common Python types `int`, `float` and `bool` and they are denoted by `int`, `real` and `bool` in the native syntax and by `CInt`, `CReal` and `CBool` respectively in the Python SDK. In addition, arrays of classical values are also supported as well as more configurable data structures called `Struct`s (which are the equivalent of Python dataclasses without methods). The full list of all the classical variables and types supported in Qmod can be found [here](https://docs.classiq.io/latest/reference-manual/platform/qmod/language-reference/classical-types/#structs). \n",
    "\n",
    "For now, the only two classical operations supported in Qmod are `repeat` which is the equivalent of a Python `for` loop and `if-else` statements. The detailed description of both can be found [here](https://docs.classiq.io/latest/reference-manual/platform/qmod/language-reference/statements/classical-control-flow/).\n",
    "\n",
    "Let's understand how to manage classical control flow in Qmod for our benefit through the following concrete example."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Concrete Example"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Our task is to create a quantum array with $10$ qubits in the state of $|1010101010\\rangle$, i.e. a quantum state with alternating qubit states of zeros and ones. \n",
    "\n",
    "How shell we approach this? Knowing that a general qubit array can be easily initialized to the state of all zeros i.e. $|0000000000\\rangle$, we can see that we need to flip the state of every second qubit, i.e. of all the qubits in an even position (the qubit in the $0th$ position, $2nd$ position, $4th$ position etc.).\n",
    "\n",
    "So we will approach this task in two steps. First we will create a function called `apply_condition` that receives a qubit and its position within the qubit array, and if the position is even then it flips its state. Secondly, we will go over all the qubits in the qubit array and apply the function on each qubit.\n",
    "\n",
    "The `apply_condition` function receives a qubit variable named `qubit` and a classical integer variable `index`, and if the condition that the variable `index` is an even number, then there is a `X` gate applied on the `qubit` which flips its state."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "from classiq import *\n",
    "\n",
    "\n",
    "@qfunc\n",
    "def apply_condition(index: CInt, qubit: QBit):\n",
    "    if_(condition=index % 2 == 0, then=lambda: X(qubit))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The condition of wether the variable `index` is even is evaluated using the modulo operation `%`, that calculates the reminder of the integer `index` divided by $2$. One can notice that in the Python SDK there is a use of the `lambda:` construct before the operation `X(qubit)`. As described in the [Quantum Variables and Functions Tutorial](../quantum_variables_and_functions/quantum_variables_and_functions.ipynb), in the Python SDK every function that is given as an argument for another function needs to have the prefix of `lambda:`.\n",
    "\n",
    "The argument of the `main` function is a qubit array named `x`, and it is initalized to the state $|0000000000\\rangle$ with $10$ qubits using the `allocate` function. Then the classical operator `repeat` is applied, where function `apply_condition` is applied $10$ times, one time per qubit in the qubit array `x`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "@qfunc\n",
    "def main(x: Output[QArray]):\n",
    "    allocate(10, x)\n",
    "    repeat(count=x.len, iteration=lambda index: apply_condition(index, x[index]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The `repeat` operator has two arguments. First, the number of repetitions is specified, and in this case and it set to the length of the qubit array `x`. Secondly, the statement (or statements) that are being repeated are specified, and in the above it is the `apply_condition` function with the two arguments `index` and the corresponding qubit `x[index]`. One may notice the different syntax between the Native syntax and the Python SDK in declaring the counting index with the variable `index`.\n",
    "\n",
    "That's it, the algorithm is ready and now it can be synthesized and viewed:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Opening: https://platform.classiq.io/circuit/578f79f5-1801-4cde-874b-79914c46b89a?version=0.40.0\n"
     ]
    }
   ],
   "source": [
    "quantum_program = synthesize(create_model(main))\n",
    "show(quantum_program)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<div  style=\"text-align:center;\">\n",
    "    <img src=\"https://docs.classiq.io/resources/classical_operations.gif\n",
    "\">\n",
    "</div>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And the quantum program can be executed on the default simulator in order to evaluate the results:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'0101010101': 2048}\n"
     ]
    }
   ],
   "source": [
    "results = execute(quantum_program).result()[0].value\n",
    "print(results.counts)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<div  style=\"text-align:center;\">\n",
    "    <img src=\"https://docs.classiq.io/resources/classical_operations_execute.gif\n",
    "\">\n",
    "</div>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The only measured bit string is $0101010101$, which with the Classiq notation should be read from right to left when interperted, hence it corresponds to the state $|1010101010\\rangle$ which is exactly what we have tried to achieve!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Summary - Classical Variables and Operations"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The following summarizes the above example in terms of classical variables and operations:\n",
    "\n",
    "* Qmod supports the use of classical variables. In the above example, the integer variable `index` is explicitly declared and used. The length of the qubit array `x` is also a classical integer variable that is used implicitly within the `repeat` operation.\n",
    "\n",
    "* Qmod supports the use of classical operations that receive classical variables and quantum operations that are applied according to the classical operation logic. In the above example, the `if` classical operation applies the quantum operation `X(qubit)` if the classical variable `index` is even, and the classical operation `repeat` applies the quantum operation `apply_condition` 10 times - the result of evaluating `x.len`."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## All Code Together"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from classiq import *\n",
    "\n",
    "\n",
    "@qfunc\n",
    "def apply_condition(index: CInt, qubit: QBit):\n",
    "    if_(condition=index % 2 == 0, then=lambda: X(qubit))\n",
    "\n",
    "\n",
    "@qfunc\n",
    "def main(x: Output[QArray]):\n",
    "    allocate(10, x)\n",
    "    repeat(count=x.len, iteration=lambda index: apply_condition(index, x[index]))\n",
    "\n",
    "\n",
    "quantum_program = synthesize(create_model(main))\n",
    "show(quantum_program)\n",
    "\n",
    "results = execute(quantum_program).result()[0].value\n",
    "print(results.counts)\n",
    "\n",
    "write_qmod(create_model(main), \"classical_variables_and_operations\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3.11.7 ('classiq_devolpment')",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.7"
  },
  "orig_nbformat": 4,
  "vscode": {
   "interpreter": {
    "hash": "e992e515f6583afc67b46eeabcda0f30363069fab8b382c7517b274ba7a59477"
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
